using System;
using System.Xml;
using System.IO;
using System.Collections.Generic;
using System.Reflection;
using System.Diagnostics;

namespace Dida
{
	/// <summary>
	/// The base class for classes that contain settings.
	/// </summary>
	/// <remarks>
	/// <para>
	/// All public fields and properties are automatically treated as setting
	/// (using reflection). 
	/// </para>
	/// <para>
	/// It is allowed to have nested SettingsContainers.
	/// </para>
	/// </remarks>
	public abstract class SettingsContainer
	{
		/// <summary>
		/// This class lets you access a single setting, when you're
		/// enumerating through the settings, using the <see cref = "Accessors"/>
		/// property.
		/// </summary>
		/// <remarks>
		/// See the <see cref = "Accessors"/> property for an example.
		/// </remarks>
		public abstract class Accessor
		{
			/// <summary>
			/// Gets the name of the setting.
			/// </summary>
			/// <remarks>
			/// This name is the same as the name of the field/property that 
			/// defines the setting.
			/// </remarks>
			public abstract string Name { get; }

			/// <summary>
			/// Gets the type of this setting.
			/// </summary>
			/// <remarks>
			/// This is the type the field/property tat defines
			/// this setting. The actual value may be of a derived type.
			/// </remarks>
			public abstract Type SettingType { get; }

			/// <summary>
			/// Gets or sets the the value of this setting.
			/// </summary>
			/// <remarks>
			/// This setting must be of the type <see cref = "SettingType"/>
			/// (or a compatible type).
			/// </remarks>
			public abstract object Value { get; set; }
		}

		private class FieldAccessor : Accessor
		{
			SettingsContainer container;
			FieldInfo fieldInfo;

			public FieldAccessor(SettingsContainer container, FieldInfo fieldInfo)
			{
				this.container = container;
				this.fieldInfo = fieldInfo;
			}

			public override string Name
			{
				get { return fieldInfo.Name; }
			}

			public override Type SettingType
			{
				get { return fieldInfo.FieldType; }
			}

			public override object Value
			{
				get
				{
					return fieldInfo.GetValue(container);					
				}

				set
				{
					Debug.Assert(value == null || SettingType.IsInstanceOfType(value),
						"The value must be of (or derived from) the " +
						SettingType.Name + " type");
					Debug.Assert(value != null || !SettingType.IsValueType,
						"You shouldn't set a value type setting to null");

					fieldInfo.SetValue(container, value);
				}
			}
		}

		private class PropertyAccessor : Accessor
		{
			SettingsContainer container;
			PropertyInfo propInfo;

			public PropertyAccessor(SettingsContainer container, PropertyInfo propInfo)
			{
				this.container = container;
				this.propInfo = propInfo;
			}

			public override string Name
			{
				get { return propInfo.Name; }
			}

			public override Type SettingType
			{
				get { return propInfo.PropertyType; }
			}

			public override object Value
			{
				get
				{
					return propInfo.GetValue(container, null);
				}

				set
				{
					Debug.Assert(value == null || SettingType.IsInstanceOfType(value),
						"The value must be of (or derived from) the " +
						SettingType.Name + " type");
					Debug.Assert(value != null || !SettingType.IsValueType,
						"You shouldn't set a value type setting to null");

					propInfo.SetValue(container, value, null);
				}
			}
		}

		/// <summary>
		/// Gets an enumerable list of <see cref = "Accessor">Accessors</see>,
		/// which lets you access all the settings of this SettingsContainer,
		/// Note that this are the settings that are defined in the derived class.
		/// </summary>
		/// <remarks>
		/// <code language = "C#">
		/// using System;
		/// 
		/// foreach(SettingsContainer.Accessor setting in settingsContainer.accessors)
		/// {
		///		Console.WriteLine("The setting " + setting.Name + 
		///			" is of type " + setting.SettingType.Name);
		/// }
		/// </code>
		/// </remarks>
		public IEnumerable<Accessor> Accessors
		{
			get
			{
				return new AccessorsEnumerable(this);
			}
		}

		private class AccessorsEnumerable : IEnumerable<Accessor>
		{
			SettingsContainer container;

			public AccessorsEnumerable(SettingsContainer container)
			{
				this.container = container;
			}

			public IEnumerator<Accessor> GetEnumerator()
			{
				return new AccessorEnumerator(container);
			}

			System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
			{
				return new AccessorEnumerator(container);
			}
		}

		private class AccessorEnumerator : IEnumerator<Accessor>
		{
			FieldInfo[] fields;
			PropertyInfo[] properties;
			SettingsContainer container;

			bool inFields;
			int index;

			public AccessorEnumerator(SettingsContainer container)
			{
				fields = container.GetType().GetFields(
					BindingFlags.Instance|BindingFlags.Public);
				properties = container.GetType().GetProperties(
					BindingFlags.Instance|BindingFlags.Public);
				this.container = container;

				inFields = true;
				index = -1;
			}

			public Accessor Current
			{
				get 
				{
					if (inFields)
						return new FieldAccessor(container,fields[index]);
					else
						return new PropertyAccessor(container,properties[index]);
				}
			}

			object System.Collections.IEnumerator.Current
			{
				get 
				{
					return Current;
				}
			}

			public bool MoveNext()
			{
				index++;
				if (inFields)
				{
					if(index < fields.Length)
						return true;
					
					inFields = false;
					index = 0;
				}

				return index < properties.Length;
			}

			public void Reset()
			{
				index = -1;
				inFields = true;
			}

			public void Dispose()
			{
				// nothing
			}
		}

		private static void GetFlattenedSettingNamesImpl(Type containerType,List<string> ret)
		{
			FieldInfo[] fields = containerType.GetFields(BindingFlags.Instance|
				BindingFlags.Public);
			foreach(FieldInfo field in fields)
			{
				if (field.FieldType.IsSubclassOf(typeof(SettingsContainer)))
				{
					// call this method recursively on nested containers
					GetFlattenedSettingNamesImpl(field.FieldType, ret);
				}
				else
					ret.Add(field.Name);
			}

			PropertyInfo[] properties = containerType.GetProperties(
				BindingFlags.Instance | BindingFlags.Public);
			foreach(PropertyInfo property in properties)
			{
				if (property.PropertyType.IsSubclassOf(typeof(SettingsContainer)))
				{
					// call this method recursively on nested containers
					GetFlattenedSettingNamesImpl(property.PropertyType, ret);
				}
				else
					ret.Add(property.Name);
			}
		}

		/// <summary>
		/// <para>
		/// A static function that lets you get the names of all settings that
		/// are used by the given <see cref = "SettingsContainer"/> class.
		/// </para>
		/// <para>
		/// The returned list is a flattened list, which means that it includes
		/// the setting names of nested <see cref = "SettingsContainer">
		/// SettingsContainers</see>,
		/// directly on the place where the nested <see cref = "SettingsContainer"/> 
		/// is defined.
		/// </para>
		/// </summary>
		/// <param name="containerType">The type of the SettingsContainer</param>
		/// <returns>A list of names of all the settings.</returns>
		public static IList<string> GetFlattenedSettingNames(Type containerType)
		{
			Debug.Assert(containerType.IsSubclassOf(typeof(SettingsContainer)),
				"The containerType must be derived from SettingsContainer");

			List<string> ret = new List<string>();
			GetFlattenedSettingNamesImpl(containerType, ret);
			return ret;
		}
	}
}